package pt.uc.dei.mse.eai.screenScrapping;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;

import pt.uc.dei.mse.eai.model.dataModel.CastType;
import pt.uc.dei.mse.eai.model.dataModel.Cinema;
import pt.uc.dei.mse.eai.model.dataModel.ClassificationType;
import pt.uc.dei.mse.eai.model.dataModel.GenreType;
import pt.uc.dei.mse.eai.model.dataModel.GenresType;
import pt.uc.dei.mse.eai.model.dataModel.LocationType;
import pt.uc.dei.mse.eai.model.dataModel.MovieType;
import pt.uc.dei.mse.eai.model.dataModel.MovieTypeList;
import pt.uc.dei.mse.eai.model.dataModel.ObjectFactory;

/**
 * Utility class for parsing web pages' content
 * @author XPTS929
 *
 */
public class PageParser {

	/**
	 * 
	 * @param homePageContent
	 * @return
	 * 		Returns a HashMap <Cinema title, Cinema URL> based on the Cinemas listed
	 * on the home page
	 */
	public static HashMap<String, String> getCinemasLinksFromHomePage(
			String homePageContent) {

		// Will map each Cinema name to its URL in the site
		HashMap<String, String> cinemasLinks = new HashMap<String, String>();

		// Cinemas list is enclosed by 'lista-salas' div
		List<String> matches = new RegexUtils()
				.find("<div id=\"lista-salas\" class=\"submenu\">[\\s\\S]*#lista-salas -->",
						homePageContent);
		String cinemasListBlock = matches.get(0);

		// Each anchor inside the rooms section points to a different
		List<String> cinemasMatches = new RegexUtils().find(
				"<a href=[\\s\\S]*?</a>", cinemasListBlock);

		// The attribute href is the URL; the tile is the Cinema name
		for (String cinemaMatch : cinemasMatches) {
			String cinemaLink = new RegexUtils()
					.findWithGroups("<a href=\"([\\s\\S]*?)\"", cinemaMatch)
					.get(0).get(1);
			String cinemaName = new RegexUtils()
					.findWithGroups("title=\"([\\s\\S]*?)\"", cinemaMatch)
					.get(0).get(1);

			cinemasLinks.put(cinemaName, cinemaLink);
		}
		return cinemasLinks;
	}

	/**
	 * 
	 * @param cinemaPageContent
	 * @return A Cinema object, filled with every movie it is showing now
	 * @throws IOException
	 */
	public static Cinema getCinemaInfoFromCinemaPage(String cinemaPageContent,
			String siteHomePageURL) throws IOException {

		List<List<String>> groupMatches;

		// The generic Cinema info block is enclosed in the 'cinema intro'
		// header and following div
		List<String> matches = new RegexUtils()
				.find("<header class=\"cinema intro\">[\\s\\S]*?</div><!-- #content -->",
						cinemaPageContent);
		String cinemaInfoBlock = matches.get(0);

		// The address is in the row that starts with 'Morada'
		String morada = "", googleLink = "";
		groupMatches = new RegexUtils().findWithGroups(
				"<dt>Morada:</dt><dd>([\\s\\S]*?)</dd>", cinemaInfoBlock);
		if (groupMatches.size() > 0) {
			String moradaBlock = groupMatches.get(0).get(1);

			// the geolocation is the link in the Morada
			groupMatches = new RegexUtils().findWithGroups(
					"\\(<a href=\"([\\s\\S]*?)\"", moradaBlock);
			if (groupMatches.size() > 0) {
				googleLink = groupMatches.get(0).get(1);
			}

			// the address itself is what preceeds the geolocation
			groupMatches = new RegexUtils().findWithGroups(
					"([\\s\\S]*?)\\(<a href", moradaBlock);
			if (groupMatches.size() > 0) {
				morada = groupMatches.get(0).get(1);
			}
		}

		// The Cinema name is inside <h2></h2> tags
		String title = "";
		groupMatches = new RegexUtils().findWithGroups("<h2>([\\s\\S]*?)</h2>",
				cinemaInfoBlock);
		if (groupMatches.size() > 0) {
			title = groupMatches.get(0).get(1);
		}

		// Telefone para marcações
		String telephone = "";
		groupMatches = new RegexUtils()
				.findWithGroups(
						"<dt>Telefone para Marca&ccedil;&otilde;es:</dt><dd>([\\s\\S]*?)</dd>",
						cinemaInfoBlock);
		if (groupMatches.size() > 0)
			telephone = groupMatches.get(0).get(1);

		// build the Location object: Cinema name, address, geolocation and
		// telephone
		LocationType loc = new LocationType();
		loc.setAddress(morada);
		loc.setTitle(title);
		loc.setGeolocation(googleLink);
		loc.getTelephone().add(telephone);

		// build the Cinema object
		Cinema cine = new Cinema();
		MovieTypeList movies = new MovieTypeList();
		cine.setMovies(movies);
		cine.setLocation(loc);

		// Find the movies block, enclosed inside the 'cinema' class ul
		String moviesBlock = "";
		matches = new RegexUtils()
				.find("<ul class=\"cinema basicvertlist stripelist\">[\\s\\S]*?</div><!-- #content -->",
						cinemaInfoBlock);
		if (matches.size() > 0)
			moviesBlock = matches.get(0);

		// Each movie is identified by an anchor whose link starts with '/Filme'
		List<String> movieMatches = new RegexUtils().find(
				"<a href=\"[\\s\\S]*?</ul>", moviesBlock);

		// This anchor includes the movie link, title and image links
		// Each one is enclosed by a new pair of ""
		// Get each match and discard begin and end "
		for (String movieBlock : movieMatches) {
			matches = new RegexUtils().find("\"[\\s\\S]*?\"", movieBlock);

			String movieLink = "", movieTitle = "", imageLink = "";
			if (matches.size() > 0) {
				movieLink = matches.get(0).substring(1,
						matches.get(0).length() - 1);
				// remove relative paths
				if (movieLink.startsWith("../"))
					movieLink = movieLink.substring(3);
				// add fixed part, if not there already
				if (!movieLink.startsWith("http"))
					movieLink = siteHomePageURL + movieLink;
				
			}

			if (matches.size() > 1)
				movieTitle = matches.get(1).substring(1,
						matches.get(1).length() - 1);
			if (matches.size() > 2) {
				imageLink = matches.get(2).substring(1,
						matches.get(2).length() - 1);
				if (!imageLink.startsWith("http"))
					imageLink = siteHomePageURL + imageLink;
			}

			// Director
			String director = "";
			matches = new RegexUtils().find(
					"<li><strong>De:</strong>[\\s\\S]*?</li>", movieBlock);

			// the director name appears as the text of the anchor
			if (matches.size() > 0) {
				String directorBlock = matches.get(0);
				groupMatches = new RegexUtils().findWithGroups(
						"\">([\\s\\S]*?)</a>", directorBlock);
				if (groupMatches.size() > 0) {
					director = groupMatches.get(0).get(1);
				}
			}

			// Casting
			CastType cast = new CastType();
			cast.getActor();
			matches = new RegexUtils().find(
					"<li><strong>Com:</strong>[\\s\\S]*?</li>", movieBlock);
			if (matches.size() > 0) {
				String starringBlock = matches.get(0);
				groupMatches = new RegexUtils().findWithGroups(
						"\">([\\s\\S]*?)</a>", starringBlock);

				for (List<String> star : groupMatches) {
					cast.getActor().add(star.get(1));
				}
			}

			// Age classification enclosed inside li>strong
			String age = "";
			groupMatches = new RegexUtils().findWithGroups(
					"<li><strong>(.*)</strong></li>", movieBlock);
			if (groupMatches.size() > 0) {
				age = groupMatches.get(0).get(1);
			}

			// Sala
			String salas = "";
			groupMatches = new RegexUtils().findWithGroups(
					"Sala</strong>:([\\s\\S]*?)</li>", movieBlock);
			if (groupMatches.size() > 0) {
				salas = groupMatches.get(0).get(1);
			}

			// Sessoes
			List<String> sessoesList = new ArrayList<String>();
			groupMatches = new RegexUtils().findWithGroups(
					"es</strong>:([\\s\\S]*?)</li>", movieBlock);
			if (groupMatches.size() > 0) {
				String sessoes = groupMatches.get(0).get(1);

				String[] sess = sessoes.split(",");
				for (int i = 0; i < sess.length; i++) {
					if ((sess[i] != null) && (sess[i].trim() != ""))
						sessoesList.add(sess[i]);
				}
			}

			// Build the Movie object
			MovieType movie = new MovieType();

			for (String str : sessoesList)
				movie.getSessions().add(str.trim());

			movie.setCast(cast);
			movie.setDirector(director);
			movie.setTitle(movieTitle);
			movie.getRoom().add(salas);
			movie.setUrl(movieLink);
			movie.setBanner(imageLink);
			movie.setClassification(parseClassification(age));

			// NOW FOLLOW THE MOVIE LINK
			// if the link is not a valid page, do not add the movie
			if (addMovieContent(movie, movieLink, siteHomePageURL))
				movies.getMovie().add(movie);
		}

		return cine;

	}

	/**
	 * Proceed to the Movie page collecting detailed info and adding it to the
	 * Movie object
	 * 
	 * Returns false if the page has no information, true otherwise
	 * 
	 * @param movie
	 * @param movieLink
	 * @throws IOException
	 */
	private static boolean addMovieContent(MovieType movie, String movieLink,
			String siteHomePageURL) throws IOException {

		ObjectFactory factory = new ObjectFactory();

		String moviePageContents = HttpClientUtils.getPage(movieLink);

		// Find the movie info block
		List<String> matches = new RegexUtils()
				.find("<div class=\"fichafilme wrap\">[\\s\\S]*?</div><!-- fichafilme wrap -->",
						moviePageContents);
		if (matches.size() == 0)
			return false;

		String movieInfoBlock = matches.get(0);

		matches = new RegexUtils()
				.find("/Trailer/[\\s\\S]*?\"", movieInfoBlock);
		if (matches.size() > 0) {
			String trailerLink = matches.get(0);
			// remove the ending "
			trailerLink = trailerLink.substring(0, trailerLink.length() - 1);
			if (!trailerLink.startsWith("http"))
				trailerLink = siteHomePageURL + trailerLink;
			movie.setTrailer(trailerLink);
		}

		// Find the original title
		List<List<String>> groupMatches = new RegexUtils().findWithGroups(
				"original:</dt><dd>([\\s\\S]*?)</dd>", movieInfoBlock);
		if (groupMatches.size() > 0) {
			movie.setOriginalTitle(groupMatches.get(0).get(1));
		}

		// Find the genres string
		groupMatches = new RegexUtils().findWithGroups(
				"nero:</dt><dd>([\\s\\S]*?)</dd>", movieInfoBlock);
		if (groupMatches.size() > 0) {
			String genero = groupMatches.get(0).get(1);

			// Genres are separated by ,
			String[] gens = genero.split(",");
			GenresType genres = factory.createGenresType();
			for (int i = 0; i < gens.length; i++) {
				GenreType genre = parseGenreType(gens[i]);
				if (genre != null)
					genres.getGenre().add(genre);
			}
			movie.setGenres(genres);
		}

		// Find the more data string
		groupMatches = new RegexUtils().findWithGroups(
				"dados:</dt><dd>([\\s\\S]*?)</dd>", movieInfoBlock);
		if (groupMatches.size() > 0) {
			String moreData = groupMatches.get(0).get(1);
			// It is expected to be in the form: EUA, 2011, Cores, 112 min.

			// Country comes first
			String[] data = moreData.split(",");
			if (data.length > 0)
				movie.setCountry(data[0]);

			// YEAR in second
			if (data.length > 1) {
				try {
					Integer yr = Integer.parseInt(data[1].trim());
					XMLGregorianCalendar year = null;
					try {
						year = DatatypeFactory.newInstance()
								.newXMLGregorianCalendar();
					} catch (DatatypeConfigurationException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					year.setYear(yr);
					movie.setYear(year);
				} catch (NumberFormatException exc) {
					// No Valid Year
				}
			}

			// Duration is fourth
			if (data.length > 3) {
				try {
					Integer dur = Integer.parseInt(data[3].trim());

					movie.setDuration(Integer.parseInt(data[3].substring(0,
							data[3].indexOf('m')).trim()));
				} catch (NumberFormatException exc) {

				}
			}
		}

		// Finally, the synopsis is enclosed in a <p></p>
		groupMatches = new RegexUtils().findWithGroups("<p>([\\s\\S]*?)</p>",
				movieInfoBlock);
		if (groupMatches.size() > 0) {
			String synop = groupMatches.get(0).get(1);

			// Eliminate some irrelevant HTML markup
			synop = synop.replace("<div>", "");
			synop = synop.replace("</div>", "");
			synop = synop.replace("<br/>", "");
			synop = synop.replace("<br />", "");
			movie.setSinopsis(synop);
		}

		return true;
	}

	/**
	 * 
	 * @param genreType
	 * @return
	 * 		Return the Genre in the string
	 */
	private static GenreType parseGenreType(String genreType) {

		if (genreType.trim().equalsIgnoreCase("Drama"))
			return GenreType.DRAMA;

		if (genreType.trim().equalsIgnoreCase("Thriller"))
			return GenreType.THRILLER;

		if (genreType.trim().equalsIgnoreCase("Terror"))
			return GenreType.TERROR;

		if (genreType.trim().equalsIgnoreCase("Com\u00e9dia"))
			return GenreType.COMÉDIA;

		if (genreType.trim().equalsIgnoreCase("Anima\u00e7\u00e3o"))
			return GenreType.ANIMAÇÃO;

		if (genreType.trim().equalsIgnoreCase("Ac\u00e7\u00e3o"))
			return GenreType.ACÇÃO;

		if (genreType.trim().equalsIgnoreCase("Romance"))
			return GenreType.ROMANCE;

		if (genreType.trim().equalsIgnoreCase(
				"Fic\u00e7\u00e3o Cient\u00edfica"))
			return GenreType.FICÇÃO_CIENTÍFICA;

		if (genreType.trim().equalsIgnoreCase("Western"))
			return GenreType.WESTERN;

		// since genre is manadtory, return a default one
		return GenreType.ACÇÃO;
	}

	/**
	 * Return the Classification in the string
	 * 
	 * @param classif
	 * @return
	 */
	private static ClassificationType parseClassification(String classif) {

		if (classif.trim().equalsIgnoreCase("M/6"))
			return ClassificationType.M_6;

		if (classif.trim().equalsIgnoreCase("M/12"))
			return ClassificationType.M_12;

		if (classif.trim().equalsIgnoreCase("M/16"))
			return ClassificationType.M_16;

		if (classif.trim().equalsIgnoreCase("M/18"))
			return ClassificationType.M_18;

		// since classification is mandatory, return a default one
		return ClassificationType.M_6;
	}
}
